using System;
using System.Collections.Generic;
using System.Text;
using Decav.Geocoding.GeocoderUs.Geocoder;
using Decav.Geography;

namespace Decav.Geocoding
{
    /// <summary>
    /// An <see cref="IGeocoder"/> that uses the http://www.geocoder.us geocoding system for
    /// converting addresses to coordinates.
    /// </summary>
    public class GeocoderUsGeocoder : IGeocoder
    {
        private GeoCode_Service _service = new GeoCode_Service();

        /// <summary>
        /// Finds an address given the specified search string, that should contain as much
        /// information as possible about the address.
        /// </summary>
        /// <param name="searchString">The address to search for, with as much detail as possible.</param>
        /// <returns>The results found for the geocoding of the specified address.</returns>
        public GeocodeAddressResult[] FindAddress(string searchString)
        {
            if (searchString == null)
                throw new ArgumentNullException("searchString");

            GeocoderAddressResult[] results = null;
            try
            {
                results = _service.geocode_address(searchString);
            }
            catch (InvalidOperationException ex)
            {
                // The geocoder throws the exception wrong and returns an object[] in WSDL, so this
                // fixes that problem by catching the document parse.  ugly.
                throw new GeocodeSearchException("The search you conducted is either not valid or could not be completed.", ex);
            }

            List<GeocodeAddressResult> resultsCast = new List<GeocodeAddressResult>();

            foreach (GeocoderAddressResult result in results)
            {
                LatLongPoint point = new LatLongPoint((decimal)result.lat, (decimal)result.@long);

                if (point == LatLongPoint.Empty)
                    continue;

                resultsCast.Add(new GeocodeAddressResult(
                    point,
                    new StreetAddress(result.number.ToString(), result.prefix, result.street, result.suffix, result.type),
                    result.city, result.state, String.Format("{0:00000}", result.zip)));
            }

            return resultsCast.ToArray();
        }

        /// <summary>
        /// Finds an intersection of two streets.
        /// </summary>
        /// <param name="searchString">The search criteria to find the intersection by.</param>
        /// <returns>The geocoded results for the intersection lookup.</returns>
        public GeocodeIntersectionResult[] FindIntersection(string searchString)
        {
            if (searchString == null)
                throw new ArgumentNullException("searchString");

            GeocoderIntersectionResult[] results = null;
            try
            {
                results = _service.geocode_intersection(searchString);
            }
            catch (InvalidOperationException ex)
            {
                // The geocoder throws the exception wrong and returns an object[] in WSDL, so this
                // fixes that problem by catching the document parse.  ugly.
                throw new GeocodeSearchException("The search you conducted is either not valid or could not be completed.", ex);
            }

            List<GeocodeIntersectionResult> resultsCast = new List<GeocodeIntersectionResult>();

            foreach (GeocoderIntersectionResult result in results)
            {
                LatLongPoint point = new LatLongPoint((decimal)result.lat, (decimal)result.@long);

                if (point == LatLongPoint.Empty)
                    continue;

                resultsCast.Add(new GeocodeIntersectionResult(
                    point,
                    new StreetAddress(null, result.prefix1, result.street1, result.suffix1, result.type1),
                    new StreetAddress(null, result.prefix2, result.street2, result.suffix2, result.type2),
                    result.city, result.state, String.Format("{0:00000}", result.zip)));
            }

            return resultsCast.ToArray();
        }

        /// <summary>
        /// Intelligently geocodes two locations.
        /// </summary>
        /// <param name="searchString">The search string to geocode by.</param>
        /// <returns>The results for the geocode.</returns>
        public GeocodeResult[] Find(string searchString)
        {
            if (searchString == null)
                throw new ArgumentNullException("searchString");

            string firstPart = searchString.Split(',')[0].ToLower();

            if (firstPart.Contains(" and ") || firstPart.Contains(" & "))
                return FindIntersection(searchString);
            else
                return FindAddress(searchString);

            #region Broken
            /*
            GeocoderResult[] results = _service.geocode(searchString);

            List<GeocodeResult> resultsCast = new List<GeocodeResult>();

            foreach (GeocoderResult result in results)
            {

                if (result.street1 == null || result.street1.Trim().Length == 0)
                {
                    resultsCast.Add(BuildAddress(result));
                }
                else
                {
                    resultsCast.Add(BuildIntersection(result));
                }
            }

            return resultsCast.ToArray();
             */
            #endregion
        }

        private GeocodeIntersectionResult BuildIntersection(GeocoderResult result)
        {
            return new GeocodeIntersectionResult(
                    new Decav.Geography.LatLongPoint((decimal)result.lat, (decimal)result.@long),
                    new StreetAddress(null, result.prefix1, result.street1, result.suffix1, result.type1),
                    new StreetAddress(null, result.prefix2, result.street2, result.suffix2, result.type2),
                    result.city, result.state, String.Format("{0:00000}", result.zip));
        }

        private GeocodeAddressResult BuildAddress(GeocoderResult result)
        {
            return new GeocodeAddressResult(
                new Decav.Geography.LatLongPoint((decimal)result.lat, (decimal)result.@long),
                new StreetAddress(result.number.ToString(), result.prefix, result.street, result.suffix, result.type),
                result.city, result.state, String.Format("{0:00000}", result.zip));
        }
    }
}
